Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

28장. Reflection

보통 우리는 코드를 이렇게 씁니다.
user.name처럼 이름을 직접 적어 값을 꺼냅니다.

그런데 가끔은
“이 객체에 어떤 프로퍼티가 있는지“조차
실행 중에 알아내야 할 때가 있습니다.

이렇게 프로그램이
실행 중에 자기 자신의 구조를 들여다보는 것을
리플렉션(Reflection)이라고 합니다.

비유하자면 이렇습니다.

리플렉션은 프로그램이
거울로 자기 모습을 비춰 보는 것입니다.
“나는 어떤 클래스지? 어떤 함수를 가졌지?”

스프링 같은 프레임워크는
이 리플렉션 위에서 동작합니다.
그 원리를 여기서 엿봅니다.


28.1 Reflection이란

리플렉션을 쓰면
클래스의 이름, 프로퍼티, 함수 등을
실행 중에 알아낼 수 있습니다.

class User(val name: String, val age: Int)

val user = User("홍길동", 20)
println(user::class)              // class User
println(user::class.simpleName)   // User

::class는 “이 객체의 클래스 정보“를 가져옵니다.
이름을 직접 적지 않고도
클래스 정보를 손에 쥘 수 있습니다.


28.2 Java Reflection과 Kotlin Reflection

리플렉션에는 두 종류가 있습니다.

  • 자바 리플렉션 : Class, Field, Method
  • 코틀린 리플렉션 : KClass, KProperty, KFunction

코틀린 리플렉션은
코틀린만의 특징(프로퍼티, null 여부 등)까지
이해합니다.

두 세계를 오갈 수도 있습니다.

val kClass = User::class          // 코틀린 KClass
val javaClass = User::class.java  // 자바 Class

코틀린 코드에서는
대개 코틀린 리플렉션을 씁니다.


28.3 KClass

KClass는 코틀린에서
“클래스 그 자체“를 나타내는 타입입니다.

::class로 얻습니다.

val kClass = User::class

println(kClass.simpleName)   // User
println(kClass.isData)       // false (data class 여부)

KClass를 통해
그 클래스의 프로퍼티, 함수, 애너테이션 등
거의 모든 정보에 접근할 수 있습니다.


28.4 Property Reflection

클래스가 가진 프로퍼티들을
목록으로 훑을 수 있습니다.

val user = User("홍길동", 20)

for (prop in User::class.memberProperties) {
    println("${prop.name} = ${prop.get(user)}")
}
// name = 홍길동
// age = 20

프로퍼티 이름을 미리 몰라도
모든 값을 꺼낼 수 있습니다.

이런 능력 덕분에
객체를 자동으로 JSON으로 바꾸거나,
로그로 남기는 도구를 만들 수 있습니다.


28.5 Function Reflection

함수도 같은 방식으로 다룰 수 있습니다.

class Greeter {
    fun hello(name: String) = "안녕, $name"
}

val greeter = Greeter()
val func = Greeter::hello

println(func.call(greeter, "코틀린"))   // 안녕, 코틀린

함수 이름을 문자열이 아닌
실제 참조로 다루고,
call로 실행할 수 있습니다.


28.6 Annotation 조회하기

27장에서 붙인 애너테이션은
리플렉션으로 읽어야 활용됩니다.

이때 그 애너테이션은
RUNTIME으로 유지되어야 합니다.

@Retention(AnnotationRetention.RUNTIME)
annotation class Role(val name: String)

@Role("ADMIN")
class AdminService

val role = AdminService::class.annotations
    .filterIsInstance<Role>()
    .firstOrNull()

println(role?.name)   // ADMIN

“이 클래스에 어떤 표식이 붙어 있나“를
실행 중에 확인하는 것입니다.

프레임워크는 바로 이 방식으로
@Controller, @Service 같은 표식을 찾아냅니다.


28.7 Reflection의 비용과 사용 시 주의점

리플렉션은 강력하지만 공짜가 아닙니다.

  • 일반 호출보다 느립니다
  • 컴파일 시점의 안전성이 약해집니다 (오타가 나도 실행 중에야 오류가 드러남)
  • 코드가 이해하기 어려워질 수 있습니다

그래서 원칙은 이렇습니다.

평범한 코드는 리플렉션 없이 짠다.
리플렉션은 “이름을 미리 알 수 없는” 경우에만 쓴다.

대부분의 일상 코드에서는
리플렉션이 필요 없습니다.


28.8 프레임워크가 객체를 다루는 방식 이해하기

이제 큰 그림이 보입니다.

스프링 같은 프레임워크는
우리가 만든 클래스를 미리 알지 못합니다.
그런데도 우리 객체를 만들고 연결해 줍니다.

어떻게 가능할까요?

바로 리플렉션 덕분입니다.

  • @Service 같은 애너테이션을 리플렉션으로 찾고
  • 그 클래스의 생성자를 리플렉션으로 확인하고
  • 필요한 객체를 만들어 넣어 줍니다

이 과정을 이해하면
“프레임워크가 마법이 아니라
리플렉션이라는 기술 위에 서 있다“는 것을
알게 됩니다.

리플렉션을 직접 쓸 일은 적지만,
이 원리를 알면 프레임워크가 훨씬 덜 신비롭게 보입니다.


28장을 마치며

이 장에서 우리는 다음을 배웠습니다.

  • 리플렉션은 실행 중에 자기 구조를 들여다보는 기술이라는 점
  • KClass로 클래스 정보에 접근하는 법
  • 프로퍼티와 함수, 애너테이션을 리플렉션으로 읽는 법
  • 리플렉션은 비용이 있어 꼭 필요할 때만 쓴다는 점
  • 프레임워크가 리플렉션 위에서 동작한다는 원리

이것으로 9부(자바 생태계)를 마칩니다.
다음 10부에서는
프로젝트를 구성하고 테스트하는 법을 배웁니다.